/*
board-games:	An educational framework for board games programming.
    Copyright (C) 2008  Daniel Gutson, FuDePAN
    
    This file is part of board-games.

    board-games is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    board-games is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <poll.h>


class RxPipeHandler;
class TxPipeHandler;

class TxAlgorithm
{
public:
    void send_message(int fd, const void* msgdata, size_t size)
    {
        //std::cerr << "sending " << size << " bytes\n";
        int ret;
        ret = write(fd, &size, sizeof(size));
        
        if (ret == -1)
            throw "Pipe error when sending";

        ret = write(fd, msgdata, size);
        
        if (ret == -1)
            throw "Pipe error when sending";
        //std::cerr << "sent " << ret << " bytes\n";
    }
};

class RxAlgorithm
{
    char* buffer;
    size_t buffer_size;
    //static int current_abort_fd;
    //static const int NO_FD = -1;
    
    void reserve(size_t size)
    {
        if (size > buffer_size)
        {
            delete [] buffer;
            buffer = new char[size];
            buffer_size = size;
        }
    }
public:
    RxAlgorithm() : buffer(NULL), buffer_size(0) {}
    ~RxAlgorithm()
    {
        delete [] buffer;
    }
    
    void receive_message(int fd, void*& msgdata, size_t& size)
    {
        int bytes_read;
        
        bytes_read = read(fd, &size, sizeof(size));
        
        if (bytes_read == sizeof(size))
        {
            reserve(size);
            bytes_read = read(fd, buffer, size);
            
            if (bytes_read == size)
                msgdata = buffer;
            else
                throw "Pipe error while reading pipe from player (perhaps player exited)";
        }
        else
            throw "Pipe error while reading pipe from player (perhaps player exited)";
        
    }
};

class TimedAlgorithm
{
    pollfd fds;
    int timeout;
    static const int DEFAULT_TIMEOUT = 1000; // 1 second
protected:
    TimedAlgorithm(short int flags)
    {
        fds.events = flags;
        fds.revents = flags; //not needed
        timeout = DEFAULT_TIMEOUT;
    }
    
    void wait_fd(int fd)
    {
        int ret;
        fds.fd = fd;
        ret = poll(&fds, 1, timeout);
        if (ret==0)
            throw "Timeout reading pipe";
        else if (ret<0)
            throw "Error setting poll on pipe";    
    }
public:
    void set_timeout(size_t milliseconds)
    {
        timeout = static_cast<int>(milliseconds);
    }
    
    void disable_timeout()
    {
        timeout = -1;
    }
};

class TimedRxAlgorithm : public RxAlgorithm, public TimedAlgorithm
{
public:
    TimedRxAlgorithm() : TimedAlgorithm(POLLIN)
    {}
    
    
    void receive_message(int fd, void*& msgdata, size_t& size)
    {
        wait_fd(fd);
        RxAlgorithm::receive_message(fd, msgdata, size);
    }
};

class TimedTxAlgorithm : public TxAlgorithm, public TimedAlgorithm
{
public:
    TimedTxAlgorithm() : TimedAlgorithm(POLLOUT)
    {}
    
    
    void send_message(int fd, const void* msgdata, size_t size)
    {
        wait_fd(fd);
        TxAlgorithm::send_message(fd, msgdata, size);
    }
};

class Pipe
{
    int fd[2];
    enum { READ_FD = 0, WRITE_FD = 1 };
    
    TimedTxAlgorithm tx;
    TimedRxAlgorithm rx;
protected:
    
    void create_pipe()
    {
        if (pipe(fd) == -1)
            throw "pipe creation error";            
    }
    
    void close_write_fd()
    {
        close(fd[WRITE_FD]);
    }
    
    void close_read_fd()
    {
        close(fd[READ_FD]);
    }
    
public:
    Pipe()
    {
        //fd[0] = fd[1] = 0;
        create_pipe();
    }
    
    ~Pipe()
    {
        close_read_fd();
        close_write_fd();
    }
    
    void send_message(const void* msgdata, size_t size)
    {
        tx.send_message(fd[WRITE_FD], msgdata, size);
    }
    
    void receive_message(void*& msgdata, size_t& size)
    {
        rx.receive_message(fd[READ_FD], msgdata, size);
    }
    
    static void abort_current_connection()
    {
        //RxAlgorithm::abort_current_connection();
    }

    void set_timeout(size_t milliseconds)
    {
        rx.set_timeout(milliseconds);
        tx.set_timeout(milliseconds);
    }
    
    void disable_timeout()
    {
        rx.disable_timeout();
        tx.disable_timeout();
    }

    friend class TxPipeHandler;
    friend class RxPipeHandler;
        
};

class PipeHandler
{
    int fd;
protected:
    PipeHandler(int fd) : fd(fd) {}
    
    int get_fd() const { return fd; }

public:
    void close_fd()
    {
        if (fd != 0)
            close(fd);
        fd = 0;
    }
};

class TxPipeHandler : public PipeHandler
{
    TxAlgorithm tx;
public:
    TxPipeHandler(const Pipe& pipe) : PipeHandler(pipe.fd[Pipe::WRITE_FD]) {}

    void send_message(const void* msgdata, size_t size)
    {
        tx.send_message(get_fd(), msgdata, size);
    }
};

class RxPipeHandler : public PipeHandler
{
    RxAlgorithm rx;
public:
    RxPipeHandler(const Pipe& pipe) : PipeHandler(pipe.fd[Pipe::READ_FD]) {}

    void receive_message(void*& msgdata, size_t& size)
    {
        rx.receive_message(get_fd(), msgdata, size);
    }
};
